﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Win32;
using System.Diagnostics;
using System.Timers;
using System.Threading;

namespace PowerTriggersWcf
{
    internal static class PowerModeChangeProcessor
    {
        internal enum ProcessModes
        {
            Suspend,
            Resume,
            Test,
        }

        private static volatile Thread processQueueThread; // background thread that proceses tasks
        private static volatile AutoResetEvent resetEvent; // used by foreground thread to signal to background thread that it must process a list of tasks
        private static volatile Dictionary<ProcessModes, PowerModeRunArgs> _processQueue; // ordered list of powermode change states to process by the background thread

        static PowerModeChangeProcessor()
        {
            try
            {
                _processQueue = new Dictionary<ProcessModes, PowerModeRunArgs>();

                resetEvent = new AutoResetEvent(false); // this will tell the background thread that nothing is waiting to be processed so it should wait for a signal

                ThreadStart start = new ThreadStart(ProcessQueue);
                processQueueThread = new Thread(start);
                processQueueThread.IsBackground = true; // make sure this thread does not block the process from exiting
                processQueueThread.Priority = ThreadPriority.AboveNormal; // give a higher priority in order to complete the tasks as quickly as possible
                processQueueThread.Name = "PowerModeChangeProcessor";
                processQueueThread.Start();
                // wait for the thread to get started
                while (!processQueueThread.IsAlive)
                {
                    Thread.Sleep(1);
                }
            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("PowerModeChangeProcessor ctr", ex);
            }
        }

        internal static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
        {
            try
            {
                switch (e.Mode)
                {
                    case PowerModes.Suspend:
                        AddTasksToProcessQueue(ProcessModes.Suspend, ConfigData.Default.Clone().SuspendTasks, "Suspend power state change detected");
                        break;
                    case PowerModes.Resume:
                        AddTasksToProcessQueue(ProcessModes.Resume, ConfigData.Default.Clone().ResumeTasks, "Resume power state change detected");
                        break;
                    default:
                        string modeName;
                        try
                        {
                            modeName = e.Mode.ToString();
                        }
                        catch
                        {
                            try
                            {
                                modeName = ((int)e.Mode).ToString();
                            }
                            catch
                            {
                                modeName = "Unknown PowerMode enum";
                            }
                        }
                        Service.AddHistoryHeader(HistorySeverityLevels.Informational, "Power state change event detected and ignored:" + modeName, false);
                        break;
                }
            }
            catch (System.Exception ex)
            {
                Service.AddHistoryHeader("SystemEvents_PowerModeChanged", ex);
            }
            finally
            {
                Service.SavePendingHistory();
            }
        }

        internal static void AddTasksToProcessQueue(ProcessModes processMode, Task[] tasks, string startMessage)
        {
            try
            {
                HistoryHeader historyHeader = Service.AddHistoryHeader(HistorySeverityLevels.Informational, startMessage, false);

                PowerModeRunArgs args = new PowerModeRunArgs(processMode, DateTime.UtcNow, tasks, historyHeader);

                lock (_processQueue)
                {
                    if (_processQueue.ContainsKey(processMode))
                    {
                        _processQueue.Remove(processMode);
                    }
                    _processQueue.Add(processMode, args);
                }

                try
                {
                    resetEvent.Set(); // signal to background thread that it should start processing the queue
                }
                catch (System.Exception ex)
                {
                    EventLogHelper.LogError("AddToProcessQueue1", ex);
                }
            }
            catch (System.Exception ex)
            {
                EventLogHelper.LogError("AddProcessToQueue2", ex);
                throw;
            }
        }

        private static void ProcessQueue()
        {
            do
            {
                try
                {
                    PowerModeRunArgs args = null;
                    do
                    {
                        try
                        {
                            args = null;
                            lock (_processQueue)
                            {
                                if (_processQueue.Count > 0)
                                {
                                    var kvp = _processQueue.OrderBy(arg => arg.Value.DateTime).FirstOrDefault();
                                    _processQueue.Remove(kvp.Key);
                                    args = kvp.Value;
                                }
                            }

                            if (args != null)
                            {
                                RunTasks(args);
                            }
                        }
                        catch (ThreadAbortException)
                        {
                            throw;
                        }
                        catch (System.Exception ex)
                        {
                            if (args == null)
                            {
                                Service.AddHistoryHeader("ProcessQueue Error1", ex);
                            }
                            else
                            {
                                Service.AddHistory(args.HistoryHeader, "ProcessQueue Error1", ex);
                            }
                        }
                    } while (args != null);

                    Service.SavePendingHistory();

                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

                    try
                    {
                        // wait to receive a signal that a powermode has been added to the queue
#if DEBUG
                        resetEvent.WaitOne(); 
#else
                        // allow the thread to run every 60 seconds.  
                        // this will help to keep the service application active and therefore minimese the use of virtual memory. 
                        // this will help keep the service reponsive to power mode state changes.
                        resetEvent.WaitOne(60000);
#endif
                    }
                    catch { }
                }
                catch (ThreadAbortException)
                {
                    throw;
                }
                catch (System.Exception ex)
                {
                    Service.AddHistoryHeader("ProcessQueue Error2", ex);
                    lock (_processQueue)
                    {
                        _processQueue.Clear();
                    }
                }
            } while (true); // loop ad infinitum
        }

        private static void RunTasks(PowerModeRunArgs args)
        {
            try
            {
                bool allSucceeded = true;

                if (args.Tasks == null || args.Tasks.Length == 0)
                {
                    Service.AddHistory(args.HistoryHeader, HistorySeverityLevels.Informational, "No tasks defined", false);
                }
                else
                {
                    foreach (Task task in args.Tasks)
                    {
                        try
                        {
                            if (!task.PerformAction(args.HistoryHeader))
                            {
                                allSucceeded = false;
                            }
                        }
                        catch (System.Exception ex)
                        {
                            Service.AddHistory(args.HistoryHeader, "Unhandled error processing task " + task.Description, ex);
                            allSucceeded = false;
                        }
                    }

                    if (allSucceeded)
                    {
                        Service.AddHistory(args.HistoryHeader, HistorySeverityLevels.Informational, "Tasks completed successfully", false);
                    }
                    else
                    {
                        Service.AddHistory(args.HistoryHeader, HistorySeverityLevels.Informational, "Tasks completed. One or more tasks failed", false);
                    }
                }
            }
            catch (System.Exception ex)
            {
                Service.AddHistory(args.HistoryHeader, "Unhandled error in RunTask", ex);
            }
        }
    }

    internal class PowerModeRunArgs
    {
        private PowerModeRunArgs() { }

        public PowerModeRunArgs(PowerTriggersWcf.PowerModeChangeProcessor.ProcessModes processMode, DateTime dateTime, Task[] tasks, HistoryHeader historyHeader)
        {
            ProcessMode = processMode;
            DateTime = dateTime;
            Tasks = tasks;
            HistoryHeader = historyHeader;
        }

        internal PowerTriggersWcf.PowerModeChangeProcessor.ProcessModes ProcessMode { get; private set; }

        internal DateTime DateTime { get; private set; }

        internal Task[] Tasks { get; private set; }

        internal HistoryHeader HistoryHeader { get; private set; }
    }
}
